/* jcifs smb client library in Java
 * Copyright (C) 2000  "Michael B. Allen" <jcifs at samba dot org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package jcifs.smb1.smb1;

import jcifs.smb1.util.LogStream;

class SmbTree {

    private static int tree_conn_counter;

    /* 0 - not connected
     * 1 - connecting
     * 2 - connected
     * 3 - disconnecting
     */
    int connectionState;
    int tid;

    String share;
    String service = "?????";
    String service0;
    SmbSession session;
    boolean inDfs, inDomainDfs;
    int tree_num; // used by SmbFile.isOpen

    SmbTree(final SmbSession session, final String share, final String service) {
        this.session = session;
        this.share = share.toUpperCase();
        if (service != null && !service.startsWith("??")) {
            this.service = service;
        }
        this.service0 = this.service;
        this.connectionState = 0;
    }

    boolean matches(final String share, final String service) {
        return this.share.equalsIgnoreCase(share)
                && (service == null || service.startsWith("??") || this.service.equalsIgnoreCase(service));
    }

    @Override
    public boolean equals(final Object obj) {
        if (obj instanceof final SmbTree tree) {
            return matches(tree.share, tree.service);
        }
        return false;
    }

    void send(final ServerMessageBlock request, final ServerMessageBlock response) throws SmbException {
        synchronized (session.transport()) {
            if (response != null) {
                response.received = false;
            }
            treeConnect(request, response);
            if (request == null || response != null && response.received) {
                return;
            }
            if (!service.equals("A:")) {
                switch (request.command) {
                case ServerMessageBlock.SMB_COM_OPEN_ANDX:
                case ServerMessageBlock.SMB_COM_NT_CREATE_ANDX:
                case ServerMessageBlock.SMB_COM_READ_ANDX:
                case ServerMessageBlock.SMB_COM_WRITE_ANDX:
                case ServerMessageBlock.SMB_COM_CLOSE:
                case ServerMessageBlock.SMB_COM_TREE_DISCONNECT:
                    break;
                case ServerMessageBlock.SMB_COM_TRANSACTION:
                case ServerMessageBlock.SMB_COM_TRANSACTION2:
                    switch (((SmbComTransaction) request).subCommand & 0xFF) {
                    case SmbComTransaction.NET_SHARE_ENUM:
                    case SmbComTransaction.NET_SERVER_ENUM2:
                    case SmbComTransaction.NET_SERVER_ENUM3:
                    case SmbComTransaction.TRANS_PEEK_NAMED_PIPE:
                    case SmbComTransaction.TRANS_WAIT_NAMED_PIPE:
                    case SmbComTransaction.TRANS_CALL_NAMED_PIPE:
                    case SmbComTransaction.TRANS_TRANSACT_NAMED_PIPE:
                    case SmbComTransaction.TRANS2_GET_DFS_REFERRAL:
                        break;
                    default:
                        throw new SmbException("Invalid operation for " + service + " service");
                    }
                    break;
                default:
                    throw new SmbException("Invalid operation for " + service + " service" + request);
                }
            }
            request.tid = tid;
            if (inDfs && !service.equals("IPC") && request.path != null && request.path.length() > 0) {
                /* When DFS is in action all request paths are
                 * full UNC paths minus the first backslash like
                 *   \server\share\path\to\file
                 * as opposed to normally
                 *   \path\to\file
                 */
                request.flags2 = SmbConstants.FLAGS2_RESOLVE_PATHS_IN_DFS;
                request.path = '\\' + session.transport().tconHostName + '\\' + share + request.path;
            }
            try {
                session.send(request, response);
            } catch (final SmbException se) {
                if (se.getNtStatus() == NtStatus.NT_STATUS_NETWORK_NAME_DELETED) {
                    /* Someone removed the share while we were
                     * connected. Bastards! Disconnect this tree
                     * so that it reconnects cleanly should the share
                     * reappear in this client's lifetime.
                     */
                    treeDisconnect(true);
                }
                throw se;
            }
        }
    }

    void treeConnect(final ServerMessageBlock andx, final ServerMessageBlock andxResponse) throws SmbException {

        synchronized (session.transport()) {
            String unc;

            while (connectionState != 0) {
                if (connectionState == 2 || connectionState == 3) { // connected or disconnecting
                    return;
                }
                try {
                    session.transport.wait();
                } catch (final InterruptedException ie) {
                    throw new SmbException(ie.getMessage(), ie);
                }
            }
            connectionState = 1; // trying ...

            try {
                /* The hostname to use in the path is only known for
                 * sure if the NetBIOS session has been successfully
                 * established.
                 */

                session.transport.connect();

                unc = "\\\\" + session.transport.tconHostName + '\\' + share;

                /* IBM iSeries doesn't like specifying a service. Always reset
                 * the service to whatever was determined in the constructor.
                 */
                service = service0;

                /*
                 * Tree Connect And X Request / Response
                 */

                if (LogStream.level >= 4) {
                    SmbTransport.log.println("treeConnect: unc=" + unc + ",service=" + service);
                }

                final SmbComTreeConnectAndXResponse response = new SmbComTreeConnectAndXResponse(andxResponse);
                final SmbComTreeConnectAndX request = new SmbComTreeConnectAndX(session, unc, service, andx);
                session.send(request, response);

                tid = response.tid;
                service = response.service;
                inDfs = response.shareIsInDfs;
                tree_num = tree_conn_counter++;

                connectionState = 2; // connected
            } catch (final SmbException se) {
                treeDisconnect(true);
                connectionState = 0;
                throw se;
            }
        }
    }

    void treeDisconnect(final boolean inError) {
        synchronized (session.transport()) {

            if (connectionState != 2) { // not-connected
                return;
            }
            connectionState = 3; // disconnecting

            if (!inError && tid != 0) {
                try {
                    send(new SmbComTreeDisconnect(), null);
                } catch (final SmbException se) {
                    if (LogStream.level > 1) {
                        se.printStackTrace(SmbTransport.log);
                    }
                }
            }
            inDfs = false;
            inDomainDfs = false;

            connectionState = 0;

            session.transport.notifyAll();
        }
    }

    @Override
    public String toString() {
        return "SmbTree[share=" + share + ",service=" + service + ",tid=" + tid + ",inDfs=" + inDfs + ",inDomainDfs=" + inDomainDfs
                + ",connectionState=" + connectionState + "]";
    }
}
